package com.zonski.jbots.game.entity;

import java.util.Random;
import java.util.Vector;

import com.zonski.jbots.game.JBotsConstants;
import com.zonski.jbots.game.EntityHelper;

import com.zonski.jbots.engine.*;
import com.zonski.jbots.engine.entity.MovingEntity;
import com.zonski.jbots.engine.entity.AbstractMind;
import com.zonski.jbots.engine.entity.Mind;

/**
 * Mind for bad robots
 */
public class SeekerBotMind extends AbstractMind
{
    protected Random random = JBotsConstants.RANDOM;
    protected int collisionFactor;

    private int targetDirection;
    private boolean hitSomething;
    private int lastX;
    private int lastY;
    /**
     * The number of updates between the death of the entity and the
     * removal of the entity from the game, defaults to 0
     */
    protected int deathUpdates;

    protected boolean createCharge;

    private static final int NUM_DIRS = 4;

    public SeekerBotMind()
    {
    }

    public void update(Engine engine)
        throws Exception
    {
        if(this.getEntity().getMode() == Modes.DEAD)
        {
            if(this.getEntity().getEffectiveModeUpdates() > deathUpdates)
            {
                this.getEntity().setRemovable(true);
                // create a charge
                if(this.createCharge)
                {
                    // put down some goodies
                    Entity charge = EntityHelper.addEntity(JBotsConstants.CHARGE, this.getEntity(), engine, 0);
                    EntityHelper.centreAtClosestTile(
                            charge.bounds, this.getEntity().bounds, engine.getRoom()
                    );
                }
            }
            return;
        }
        Room room = engine.getRoom();
        MovingEntity entity = (MovingEntity)getEntity();
        int speed;
        int x = entity.bounds.getCentreX();
        int y = entity.bounds.getCentreY();
        int tileSize = room.getTileSize();
        switch(entity.direction)
        {
            case Directions.EAST:
                speed = tileSize - ((x+tileSize/2) % tileSize) - 1;
                break;
            case Directions.WEST:
                speed = (x+tileSize/2) % tileSize;
                break;
            case Directions.NORTH:
                speed = tileSize - ((y+tileSize/2) % tileSize) - 1;
                break;
            case Directions.SOUTH:
                speed = (y+tileSize/2) % tileSize;
                break;
            default:
                speed = 0;
                break;
        }
        int tx = x / tileSize;
        int ty = y / tileSize;
        if(entity.getMode() == Modes.NONE || speed <= 0 || x == this.lastX && y == this.lastY)
        {
            if(x == this.lastX && y == this.lastY)
            {
                this.collisionFactor = (this.collisionFactor + 1) * 2;
            }
            createTarget(tx, ty, engine.getStateMatrix());
            if(entity.getMode() != Modes.NONE)
            {
                speed = engine.getRoom().getTileSize();
            }else{
                speed = 0;
            }
            if(this.targetDirection != Directions.NONE)
            {
                if(entity.getTargetDirection() != this.targetDirection)
                {
                    // we've changed direction
                    this.collisionFactor = 0;
                }
                entity.setTargetDirection(this.targetDirection);
            }
        }
        entity.setTargetVelocity(speed);
        this.lastX = x;
        this.lastY = y;
    }

    protected void directionDecided(int direction, int desirability, int minDesirability)
    {
        // do nothing, this method should be overridden for custom handling
    }

    private void createTarget(int tx, int ty, RoomStateMatrix matrix)
    {
        Entity entity = getEntity();
        Room room = matrix.getRoom();
        // "decide" on a direction to go
        int r = 0;

        // default to doing nothing
        int maxDesirability = getDesirability(matrix.getEntitiesOnTile(tx, ty), 0, Directions.NONE);
        int mode = Modes.NONE;
        int maxDirection = Directions.NONE;
        int minDesirability = maxDesirability;

        while(r < NUM_DIRS)
        {
            int rdx, rdy, rdirection;
            switch(r)
            {
                default:
                case 0:
                    if(tx != room.getNumberOfTilesAcross() - 1)
                    {
                        rdx = 1;
                        rdy = 0;
                        rdirection = Directions.EAST;
                    }else{
                        rdirection = Directions.NONE;
                        rdx = 0;
                        rdy = 0;
                    }
                    break;
                case 1:
                    if(tx != 0)
                    {
                        rdx = -1;
                        rdy = 0;
                        rdirection = Directions.WEST;
                    }else{
                        rdx = 0;
                        rdy = 0;
                        rdirection = Directions.NONE;
                    }
                    break;
                case 2:
                    if(ty != room.getNumberOfTilesDown() - 1)
                    {
                        rdx = 0;
                        rdy = 1;
                        rdirection = Directions.NORTH;
                    }else{
                        rdx = 0;
                        rdy = 0;
                        rdirection = Directions.NONE;
                    }
                    break;
                case 3:
                    if(ty != 0)
                    {
                        rdx = 0;
                        rdy = -1;
                        rdirection = Directions.SOUTH;
                    }else{
                        rdx = 0;
                        rdy = 0;
                        rdirection = Directions.NONE;
                    }
                    break;
            }
            if(rdirection != Directions.NONE)
            {
                int rdesirability = getDesirability(rdx, rdy, tx, ty, matrix, rdirection);
                if(rdirection == this.getEntity().direction)
                {
                    if(this.hitSomething)
                    {
                        if(this.collisionFactor <= 0)
                        {
                            this.collisionFactor = 1;
                        }else{
                            this.collisionFactor *= 4;
                        }
                        this.hitSomething = false;
                    }else{
                        this.collisionFactor /= 2;
                    }
                    rdesirability -= this.collisionFactor;
                }
                if(rdesirability > maxDesirability)
                {
                    maxDirection = rdirection;
                    maxDesirability = rdesirability;
                    mode = Modes.WALKING;
                }else if(rdesirability < minDesirability){
                    minDesirability = rdesirability;
                }
            }
            r++;
        }
        directionDecided(maxDirection, maxDesirability, minDesirability);
        targetDirection = maxDirection;
        getEntity().setMode(mode);
    }

    protected final int getDesirability(int dx, int dy, int tx, int ty, RoomStateMatrix matrix, int direction)
    {
        int desirability = 0;
        int distance = 0;
        Room room = matrix.getRoom();

        int x = tx;
        int y = ty;
        outer: while(true)
        {
            distance ++;
            x += dx;
            y += dy;

            if(x < 0)
            {
                x = 0;
                break;
            }
            if(x >= room.getNumberOfTilesAcross())
            {
                x = room.getNumberOfTilesAcross()-1;
                break;
            }
            if(y < 0)
            {
                y = 0;
                break;
            }
            if(y >= room.getNumberOfTilesDown())
            {
                y = room.getNumberOfTilesDown()-1;
                break;
            }
            QuickVector entities = matrix.getEntitiesOnTile(x, y);
            // always stop on meeting an immovable object
            if(entities != null)
            {
                for(int i=entities.size(); i > 0; )
                {
                    i--;
                    Entity found = (Entity)entities.elementAt(i);
                    if(isBlocker(found))
                    {
                        if(distance == 1)
                        {
                            // we didn't see nothing, can't go this way
                            desirability -= 2000;
                        }
                        break outer;
                    }
                }
            }
            desirability += getDesirability(entities, distance, direction);
        }
        return desirability;
    }

    protected boolean isBlocker(Entity entity)
    {
        return JBotsConstants.isObstacle(entity.entityType.getId());
    }

    /**
     * override this method to adjust desirability for 
     * individual robot types, if the option is more desirable
     * than standing still then the value must be over 0
     */
    protected int getDesirability(QuickVector entities, int distance, int direction)
    {
        int size;
        if(entities != null)
        {
            size = entities.size();
        }else{
            size = 0;
        }
        return distance - size;
    }

    public void collision(Engine engine, CollisionData data)
    {
        // ran into something, re-evaluate desirability
        this.hitSomething = data.with.layerId != this.getEntity().layerId || this.hitSomething;
    }

    public Mind copy()
    {
        return new SeekerBotMind();
    }
}
